package br.com.srv.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import br.com.srv.config.ConfiguracaoSpring;
import br.com.srv.dao.PontoDAO;
import br.com.srv.exception.FFServiceException;
import br.com.srv.util.DistanciaComparator;

public class TratarPontoImpl implements TratarPonto {
	
	/**
	 * Raio médio da Terra=6366.707 Km (diferente do raio equatorial que é de 6371.315 km).
	 */
	public static double RaioDaTerra = 6366.707*1000;

	/**
	 * Constante para conversão de Graus para Radianos.
	 */
	public static double GR = Math.PI/180;
	
	/**
	 * DAO para trabalhar com pontos
	 */
	private PontoDAO pontoDAO;
	
	private String longitude;
	private String latitude;
	private boolean isPrimeiraVezRecursao = true;
	private Integer count = 1;
	
	public TratarPontoImpl() {
		pontoDAO = (PontoDAO) ConfiguracaoSpring.getBean(PontoDAO.SERVICE_NAME);
	}
	
	public Map<String, String> pegarPontoMaisPróximo(String longitude, String latitude) throws FFServiceException {
		
		Map<String, String> result = new HashMap<String, String>();
		
		final int TAMANHO_MINIMO = 6; 
		
		if (isPrimeiraVezRecursao) {
			this.longitude = longitude;
			this.latitude = latitude;
			isPrimeiraVezRecursao = false;
		}
		
		String longitudeParam = decrementarCoordenada(this.longitude);
		String latitudeParam = decrementarCoordenada(this.latitude);
		count++;
		
		List<Map<String, String>> listaPontos = getPontoDAO().buscarPontoLikeCoordenada(longitudeParam, latitudeParam);
		
		if (listaPontos.size() > 0 && listaPontos.size() <= TAMANHO_MINIMO) {
			
			List<Map<String, String>> listaDistancia = new ArrayList<Map<String, String>>();
				
			//Verificar qual é o mais próximo e a distância
			for (Map<String, String> mapaPonto : listaPontos) {
				Map<String, String> mapaDistancia = new HashMap<String, String>();
				mapaDistancia.put("ID", mapaPonto.get("ID"));
				mapaDistancia.put("DISTANCIA", distanciaEntrePontos(
						this.longitude, this.latitude, mapaPonto.get("LNG"), mapaPonto.get("LAT")).toString());
				
				listaDistancia.add(mapaDistancia);
			}
			
			Collections.sort(listaDistancia, new DistanciaComparator());
			
			//pega o ponto com a menor distância
			result = listaDistancia.get(0);
			
		} else if (listaPontos.size() > TAMANHO_MINIMO) { 
			count--;
			longitudeParam = decrementarCoordenada(this.longitude);
			latitudeParam = decrementarCoordenada(this.latitude);
			
			listaPontos = getPontoDAO().buscarPontoLikeCoordenada(longitudeParam, latitudeParam);
			
			List<Map<String, String>> listaDistancia = new ArrayList<Map<String, String>>();
				
			//Verificar qual é o mais próximo e a distância
			for (Map<String, String> mapaPonto : listaPontos) {
				Map<String, String> mapaDistancia = new HashMap<String, String>();
				mapaDistancia.put("ID", mapaPonto.get("ID"));
				mapaDistancia.put("DISTANCIA", distanciaEntrePontos(
						this.longitude, this.latitude, mapaPonto.get("LNG"), mapaPonto.get("LAT")).toString());
				
				listaDistancia.add(mapaDistancia);
			}
			
			Collections.sort(listaDistancia, new DistanciaComparator());
			
			//pega o ponto com a menor distância
			result = listaDistancia.get(0);
			
		} else {
			result = pegarPontoMaisPróximo(longitudeParam, latitudeParam);
		}
		
		count = 1;
		isPrimeiraVezRecursao = true;
		return result;
		
	}
	
	/**
	 * @param longitudeA
	 * @param latitudeA
	 * @param longitudeB
	 * @param latitudeB
	 * @return
	 */
    public Double distanciaEntrePontos(String longitudeA, String latitudeA,
    		String longitudeB, String latitudeB) {
    	
    	Double longitudeADouble = new Double(longitudeA);
    	Double latitudeADouble = new Double(latitudeA);
    	
    	Double longitudeBDouble = new Double(longitudeB);
    	Double latitudeBDouble = new Double(latitudeB);
    	
    	//Converter longitudes para numero de 0 a 360 (0=Greenwich)
    	if (longitudeADouble<0) longitudeADouble=360+longitudeADouble;
    	if (longitudeBDouble<0) longitudeBDouble=360+longitudeBDouble;
    	
    	latitudeADouble=90-latitudeADouble;
    	latitudeBDouble=90-latitudeBDouble;
    	
    	//Converter de Graus para radianos
    	latitudeADouble=latitudeADouble*TratarPontoImpl.GR;
    	longitudeADouble=longitudeADouble*TratarPontoImpl.GR;
    	latitudeBDouble=latitudeBDouble*TratarPontoImpl.GR;
    	longitudeBDouble=longitudeBDouble*TratarPontoImpl.GR;
    	
    	//Calcular com base em um triangulo esférico cujos pontos são:
    	//   A-Polo Norte
    	//   B-Local 1
    	//   C-Local 2
    	//Lados do Triângulo esférico: AB(lado c)  AC(lado b) BC(lado a)
    	double ab=latitudeADouble;
    	double ac=latitudeBDouble;
    	double bc= Math.abs(longitudeADouble-longitudeBDouble);
    	
    	// Aplicar a fórmula: cos(a)= cos(b).cos(c)+sen(b).sen(c).cos(a)  
    	double arcoBC=Math.acos(Math.cos(ac)*Math.cos(ab)+Math.sin(ab)*Math.sin(ac)*Math.cos(bc));
    	
    	double distancia = arcoBC*TratarPontoImpl.RaioDaTerra;
    	
    	//Arredondar deixando 1 decimal
    	distancia=Math.floor((distancia+0.05)*100)/100;
        
        return distancia;
    }
	
	private String decrementarCoordenada(String coordenada) {
		String coordenadaParam = coordenada.substring(0, coordenada.length()-count);
		return coordenadaParam;
	}
	
	public PontoDAO getPontoDAO() {
		return pontoDAO;
	}

	public void setPontoDAO(PontoDAO pontoDAO) {
		this.pontoDAO = pontoDAO;
	}
    
}
